import { cached, defineGlobalProperty } from '@/ayaka'
import RoomArray from '@/utils/roomArray'
import { RoomDangerAreaColor, RoomDangerAreaType } from './types'
import { outOfRange } from '@/utils/position'

const hash = (x: number, y: number) => (x << 6) | y

/**
 * Rampart 控制器
 */
export default class RampartController {
    /**
     * 每个房间的危险区域
     */
    protected static dangerArea: {
        [roomName: string]: {
            time: number
            dangerArea: RoomArray<RoomDangerAreaType>
        }
    } = {}

    /**
     * 每个房间 rampart 的平均血量
     */
    protected static averageRampartHits: {
        [roomName: string]: {
            time: number
            hits: number
        }
    } = {}

    /**
     * 获取我的房间的危险区域，无缓存时大约耗时 10 cpu，未考虑 ram 下面有建筑的情况，寻路时请自行判断
     *
     * TODO: 墙外如果有 rampart，虽然该位置是安全的，但是不应该出去，所以还缺少一种类型
     */
    public static getDangerArea(room: MyRoom) {
        if (RampartController.dangerArea[room.name]?.time < Game.time) {
            delete RampartController.dangerArea[room.name]
        }

        if (RampartController.dangerArea[room.name]?.dangerArea) {
            return RampartController.dangerArea[room.name].dangerArea
        }

        // 初始全是安全区域
        const dangerArea = new RoomArray<RoomDangerAreaType>(RoomDangerAreaType.safe)
        const terrain = new Room.Terrain(room.name)
        const rampartPosSet = new Set(room.rampart.map(({ pos }) => hash(pos.x, pos.y)))
        const constructedWallPosSet = new Set(room.constructedWall.map(({ pos }) => hash(pos.x, pos.y)))
        const exits = RampartController.getExitsPos(room.name)
        for (const [x, y] of exits) {
            RampartController.dfs(x, y, dangerArea, terrain, rampartPosSet, constructedWallPosSet)
        }
        // 最后设置 rampart 为安全区域
        room.rampart.forEach(({ pos }) => {
            if (dangerArea.get(pos.x, pos.y) !== RoomDangerAreaType.safe) {
                dangerArea.set(pos.x, pos.y, RoomDangerAreaType.rampart)
            }
        })

        // 设置人造墙
        room.constructedWall.forEach(({ pos }) => {
            if (dangerArea.get(pos.x, pos.y) !== RoomDangerAreaType.safe) {
                dangerArea.set(pos.x, pos.y, RoomDangerAreaType.constructedWall)
            }
        })

        // 设置自然 wall
        dangerArea.forEach((x, y) => {
            if (terrain.get(x, y) === TERRAIN_MASK_WALL) dangerArea.set(x, y, RoomDangerAreaType.wall)
        })

        RampartController.dangerArea[room.name] = {
            time: Game.time + 663,
            dangerArea,
        }

        return dangerArea
    }

    /**
     * 获取房间中 rampart 的平均厚度
     */
    public static getAverageRampartHits(room: MyRoom) {
        if (RampartController.averageRampartHits[room.name]?.time < Game.time) {
            delete RampartController.averageRampartHits[room.name]
        }

        if (RampartController.averageRampartHits[room.name]?.hits) {
            return RampartController.averageRampartHits[room.name].hits
        }

        if (!room.rampart?.length) {
            RampartController.averageRampartHits[room.name] = {
                time: Game.time + 663,
                hits: Infinity,
            }
            return Infinity
        }

        const averageRampartHits = room.rampart.reduce((pre, cur) => pre + cur.hits, 0) / room.rampart.length

        RampartController.averageRampartHits[room.name] = {
            time: Game.time + 663,
            hits: averageRampartHits,
        }

        return averageRampartHits
    }

    /**
     * dfs 搜索危险区域，规则：从房间出口开始，向里面搜索，遇到墙或者边界或者 rampart 则停止，将搜索到的区域标记为危险区域，然后继续搜索。
     * 值得注意，当搜索到 rampart 时，rampart 周围两格也会被标记为危险区域，因为敌人可能打三格远。
     */
    public static dfs(
        x: number,
        y: number,
        dangerArea: RoomArray,
        terrain: RoomTerrain,
        rampartPosSet: Set<number>,
        constructedWallPosSet: Set<number>,
    ) {
        // 越界
        if (outOfRange(x, y)) return

        const posHash = hash(x, y)
        // 搜索到自然墙 或者 rampart
        if (
            terrain.get(x, y) === TERRAIN_MASK_WALL ||
            rampartPosSet.has(posHash) ||
            constructedWallPosSet.has(posHash)
        ) {
            if (terrain.get(x, y) === TERRAIN_MASK_WALL) {
                dangerArea.set(x, y, RoomDangerAreaType.wall)
            } else if (constructedWallPosSet.has(posHash)) {
                dangerArea.set(x, y, RoomDangerAreaType.constructedWall)
            }

            for (let i = -2; i <= 2; i++) {
                for (let j = -2; j <= 2; j++) {
                    const xx = x + i
                    const yy = y + j
                    // 越界或者已经被标记为危险区域就跳过
                    if (dangerArea.get(xx, yy) !== RoomDangerAreaType.safe || outOfRange(xx, yy)) continue
                    dangerArea.set(xx, yy, RoomDangerAreaType.inside)
                }
            }
            return
        }

        // 标记为危险区域
        dangerArea.set(x, y, RoomDangerAreaType.outside)

        // 继续搜索周围八个方向
        dangerArea.forEachNear(x, y, (xx, yy, val) => {
            if (val === RoomDangerAreaType.safe || val === RoomDangerAreaType.inside) {
                RampartController.dfs(xx, yy, dangerArea, terrain, rampartPosSet, constructedWallPosSet)
            }
        })
    }

    /**
     * 获取房间中不连续的出口位置，必须是可见的房间！
     */
    public static getExitsPos = cached((roomName: string) => {
        const room = Game.rooms[roomName]
        const exitsPos: [x: number, y: number][] = []
        if (!room) return exitsPos

        // 所有出口位置，是有序排列的
        const allPos = room.find(FIND_EXIT)
        // 前一个位置
        let prePos = allPos[0]
        allPos.forEach((pos) => {
            // 不相邻
            if (pos.getRangeTo(prePos) > 1) {
                exitsPos.push([prePos.x, prePos.y])
            }
            prePos = pos
        })

        // 加入最后一个位置
        const lastPos = allPos[allPos.length - 1]
        exitsPos.push([lastPos.x, lastPos.y])

        return exitsPos
    })

    /**
     * 显示危险区域，无缓存时大约耗时：计算 10 cpu + 显示 4 cpu
     */
    public static showDangerArea(room: MyRoom) {
        const dangerArea = RampartController.getDangerArea(room)
        const visual = new RoomVisual(room.name)
        dangerArea.forEach((x, y, value) => {
            visual.circle(x, y, {
                radius: 0.5,
                fill: RoomDangerAreaColor[value] || '#ffffff',
                opacity: 0.3,
            })
        })
    }
}

defineGlobalProperty('Ram', {
    value: RampartController,
})
